package com.pig4cloud.pigx.contract.mq.listener;

import com.alibaba.druid.sql.visitor.functions.Bin;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.pig4cloud.pigx.contract.constant.ContractMqConstants;
import com.pig4cloud.pigx.contract.enums.ContractOrderEnum;
import com.pig4cloud.pigx.contract.mongo.entity.ContractOrderMongo;
import com.pig4cloud.pigx.robot.bo.RobotDepthMapMqBO;

import lombok.AllArgsConstructor;
import lombok.extern.slf4j.Slf4j;

import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.*;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.core.annotation.Order;
import org.springframework.data.redis.core.RedisTemplate;

import org.springframework.stereotype.Component;
import springfox.documentation.schema.ScalarType;

import java.math.BigDecimal;
import java.math.RoundingMode;
import java.sql.Array;
import java.util.*;

@Slf4j
@Order(10)
@Component
@AllArgsConstructor
public class DepthListener  implements ApplicationRunner {

	public Map<String, DepthEntity> depthMap = new HashMap<String,DepthEntity>();
	@Autowired
	private RedisTemplate redis;

	@Autowired
	private RabbitTemplate rabbitTemplate;



	@Override
	public void run(ApplicationArguments args) throws Exception {
		init();
	}
	private void init(){
		//depthMap = redis.opsForHash().entries("depth");

	}

	@RabbitListener(queues = ContractMqConstants.QUEUE_FANOUT_CONTRACT_ORDER_NEW_DEPTH_MAP)
	@RabbitHandler
	private void order(ContractOrderMongo msg){
		log.info("Symbol:"+msg.getSymbolName()+" price:"+msg.getPrice()+" side:"+msg.getSide());
	}
	@RabbitListener(queues = ContractMqConstants.QUEUE_FANOUT_CONTRACT_TRADE_NEW_DEPTH_MAP)
	@RabbitHandler
	private void trade(Long takerTradeId){
		log.info("takerTradeId:"+takerTradeId);
	}
	@RabbitListener(bindings = {
			@QueueBinding(
					value = @Queue(value = ContractMqConstants.QUEUE_FANOUT_CONTRACT_ORDER_NEW_ROBOT_K_LINE_DEPTH_MAP),
					exchange = @Exchange(value = ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_ORDER_NEW_ROBOT_K_LINE_DEPTH_MAP, type = ExchangeTypes.FANOUT)
			)})
	private void robotOrder(RobotDepthMapMqBO order){

		try{
			if(order.getPrice().compareTo(BigDecimal.ZERO)== 0) return;
			String mill = String.valueOf(System.currentTimeMillis());
			mill = mill.substring(7);
			log.info("-------------------------------------------------------------");
			log.info("-------------------------------------------------------------");
			log.info("---------------------"+new BigDecimal(mill).subtract(order.getNum())+"ms-----------------------------");
			log.info("-------------------------------------------------------------");
			log.info("-------------------------------------------------------------");
			log.info("-------------------------------------------------------------");

			BigDecimal lastprice = new BigDecimal(redis.opsForValue().get(order.getSymbolName()+"_last_price").toString());
//			if(order.getSide().equals(ContractOrderEnum.Side.MORE.getValue())){
//				if(order.getPrice().compareTo(lastprice)  > 0){
//					return;
//				}
//			}
//
//			if(order.getSide().equals(ContractOrderEnum.Side.EMPTY.getValue())){
//				if(order.getPrice().compareTo(lastprice)  < 0){
//					return;
//				}
//			}

			log.info("Symbol:"+order.getSymbolName()+" price:"+order.getPrice()+" side:"+order.getSide());
			//if(depthMap.get(order.getSymbolName()) == null){
				DepthEntity depthEntity = initEntityFromRedis(order.getSymbolName().toUpperCase());
				depthMap.put(order.getSymbolName(),depthEntity );
			//}

			//DepthEntity depthEntity = depthMap.get(order.getSymbolName());

			setMaxPrice(depthEntity,order.getPrice());
			setMinPrice(depthEntity,order.getPrice());

			List<String> levels = calcLevel(depthEntity.getMaxPrice(),depthEntity.getMinPrice());
			depthEntity.setLevels(levels);



			for(int i = 0 ;i<levels.size(); i++){
				String levelKey= levels.get(i);
				BigDecimal depthPrice = calcDepthPrice(levelKey,order.getPrice());
				lastprice =  calcDepthPrice(levelKey,lastprice);
				if(depthPrice.compareTo(BigDecimal.ZERO) == 0){
					continue;
				}

				//做多
				if(order.getSide().equals(ContractOrderEnum.Side.MORE.getValue())){

					TreeMap<BigDecimal,BigDecimal>  priceTreeMap = new TreeMap<BigDecimal,BigDecimal>(new Comparator<BigDecimal>() {
						@Override
						public int compare(BigDecimal o1, BigDecimal o2) {
							return o1.compareTo(o2);
						}
					});
					if(depthEntity.getMore().get(levelKey) != null){
						priceTreeMap = depthEntity.getMore().get(levelKey);
					}

					if(priceTreeMap.get(depthPrice) == null){
						if(depthPrice.compareTo(lastprice)  <= 0){
						//	priceTreeMap.put(depthPrice,order.getNum());
							log.info("-----------------depth size------------------------------");
							log.info("-----------------"+priceTreeMap.size()+"------------------------------");
						}
					}else{
						//priceTreeMap.put(depthPrice,order.getNum().add(priceTreeMap.get(depthPrice)));
					}
//					priceTreeMap = limit(priceTreeMap,lastprice,order.getSide());
//					priceTreeMap = fill(priceTreeMap,lastprice,levelKey,order.getSide(),order.getNum());
					depthEntity.getMore().put(levelKey,priceTreeMap);

				}

				//做空
				if(order.getSide().equals(ContractOrderEnum.Side.EMPTY.getValue())){
					TreeMap<BigDecimal,BigDecimal>  priceTreeMap = new TreeMap<BigDecimal,BigDecimal>(Comparator.reverseOrder());

					if(depthEntity.getEmpty().get(levelKey) != null){
						priceTreeMap = depthEntity.getEmpty().get(levelKey);
					}

					if(priceTreeMap.get(depthPrice) == null){
						if(depthPrice.compareTo(lastprice)  >= 0 ){
							//priceTreeMap.put(depthPrice,order.getNum());
							log.info("-----------------depth size------------------------------");
							log.info("-----------------"+priceTreeMap.size()+"------------------------------");
						}
					}else{
						//priceTreeMap.put(depthPrice,order.getNum().add(priceTreeMap.get(depthPrice)));
					}
//					priceTreeMap = limit(priceTreeMap,lastprice,order.getSide());
//					priceTreeMap = fill(priceTreeMap,lastprice,levelKey,order.getSide(),order.getNum());
					depthEntity.getEmpty().put(levelKey,priceTreeMap);
				}
			}


			depthMap.put(order.getSymbolName(),depthEntity);
			saveEntityToRedis(depthEntity);

			log.info("add order depth success");

		}catch (Exception ex){
			log.error("depth robot order error:"+ex.getMessage());
		}

	}

	@RabbitListener(bindings = {
			@QueueBinding(
					value = @Queue(value = ContractMqConstants.QUEUE_FANOUT_CONTRACT_TRADE_NEW_ROBOT_K_LINE_DEPTH_MAP),
					exchange = @Exchange(value = ContractMqConstants.EXCHANGE_FANOUT_CONTRACT_TRADE_NEW_ROBOT_K_LINE_DEPTH_MAP, type = ExchangeTypes.FANOUT)
			)})
	private void robotTrade(RobotDepthMapMqBO taker){
		try{
			log.info("Symbol:"+taker.getSymbolName()+" price:"+taker.getPrice()+" side:"+taker.getSide());
			if(taker.getPrice().compareTo(BigDecimal.ZERO)== 0) return;
			if(true) return ;
			//if(depthMap.get(taker.getSymbolName()) == null){
				DepthEntity depthEntity = initEntityFromRedis(taker.getSymbolName().toUpperCase());
				depthMap.put(taker.getSymbolName(),depthEntity );
			//}


		//	DepthEntity depthEntity = depthMap.get(taker.getSymbolName());

			List<String> levels = depthEntity.getLevels();
			BigDecimal lastprice = new BigDecimal(redis.opsForValue().get(taker.getSymbolName()+"_last_price").toString());
			for(int i = 0 ;i<levels.size(); i++){
				String levelKey= levels.get(i);
				BigDecimal depthPrice = calcDepthPrice(levelKey,taker.getPrice());
				lastprice =  calcDepthPrice(levelKey,lastprice);
				if(depthPrice.compareTo(BigDecimal.ZERO) == 0){
					continue;
				}

				if(taker.getSide().equals(ContractOrderEnum.Side.EMPTY.getValue())){
					TreeMap<BigDecimal,BigDecimal>  priceTreeMap = depthEntity.getEmpty().get(levelKey);
					priceTreeMap = depthEntity.getEmpty().get(levelKey);

					if(priceTreeMap != null){
						if(priceTreeMap.get(depthPrice) != null){
							BigDecimal newNum = priceTreeMap.get(depthPrice).subtract(taker.getNum());
							if(newNum.compareTo(BigDecimal.ZERO) > 0 ){
								//priceTreeMap.put(depthPrice,newNum);
							}else{
								//priceTreeMap.remove(depthPrice);
							}
						}
					}


//					priceTreeMap = limit(priceTreeMap,lastprice,taker.getSide());
					depthEntity.getEmpty().put(levelKey,priceTreeMap);

				}else{ //做多
					TreeMap<BigDecimal,BigDecimal>  priceTreeMap = depthEntity.getMore().get(levelKey);

					if(priceTreeMap != null){
						if(priceTreeMap.get(depthPrice) != null){
							BigDecimal newNum = priceTreeMap.get(depthPrice).subtract(taker.getNum());
							if(newNum.compareTo(BigDecimal.ZERO) > 0 ){
							//	priceTreeMap.put(depthPrice,newNum);
							}else{
								//priceTreeMap.remove(depthPrice);
							}//
						}
					}

//					priceTreeMap = limit(priceTreeMap,lastprice,taker.getSide());
					depthEntity.getMore().put(levelKey,priceTreeMap);

				}

				saveEntityToRedis(depthEntity);
			}

		}catch (Exception ex){
			log.error(" taker depth num error "+ ex.getMessage());
		}

	}
	public   List<TreeMap<BigDecimal,List<BigDecimal>>> getDepth(String symbol ,BigDecimal lastprice,String level,String side){
		List<TreeMap<BigDecimal,List<BigDecimal>>> list = new ArrayList<TreeMap<BigDecimal,List<BigDecimal>>>();
		list.add(getDepthSide(symbol,lastprice,level,ContractOrderEnum.Side.MORE.getValue()));
		list.add(getDepthSide(symbol,lastprice,level,ContractOrderEnum.Side.EMPTY.getValue()));

		return list;
	}
	public TreeMap<BigDecimal,List<BigDecimal>> getDepthSide(String symbol ,BigDecimal lastprice,String level,String side){
		try{

			TreeMap<BigDecimal,List<BigDecimal>> depMap = new TreeMap<BigDecimal,List<BigDecimal>>();
			depMap = getDepthFromRedis(symbol,side,level);
			depMap = limit(depMap,lastprice,side);

			Random random = new Random();

			BigDecimal num =new BigDecimal(random.nextInt(1500)+1);
			num = num.divide(lastprice,2,RoundingMode.HALF_UP);

			depMap= fill(depMap,lastprice,level,side,num);
			saveDepthToRedis(depMap,symbol,side,level);
			return depMap;
		}catch (Exception ex){
			throw  ex;
		}

	}
	private TreeMap<BigDecimal,List<BigDecimal>> fill(TreeMap<BigDecimal,List<BigDecimal>> depthMap,BigDecimal price,String level,String side,BigDecimal num){
		int limitCount = 20;
	//	if(depthMap.size() >=20 ) return  depthMap;
	//	depthMap = new TreeMap<BigDecimal,List<BigDecimal>>();
		try{
			BigDecimal depthPrice = calcDepthPrice(level,price);
			BigDecimal lastNum = BigDecimal.ZERO;
			if(side.equals(ContractOrderEnum.Side.MORE.getValue())){
				while(limitCount-- > 0){

					depthPrice = depthPrice.subtract(new BigDecimal(level)).setScale(new BigDecimal(level).scale());

					int scale = num.scale();
					Random random = new Random();
					Float f = random.nextFloat();

					BigDecimal randomNum = new BigDecimal(f.toString());

					num = num.add(randomNum);
					num = num.setScale(scale,RoundingMode.DOWN);

					if(depthMap.get(depthPrice) != null){
						List<BigDecimal> list = depthMap.get(depthPrice);
						lastNum = lastNum.add(num);

						if(lastNum.compareTo(BigDecimal.ZERO) == 0){
							lastNum = num;
						}
						list.set(0,num);
						list.set(1,lastNum);

						depthMap.put(depthPrice,list);
					}else{
						List<BigDecimal> list = new ArrayList<BigDecimal>();
						if(lastNum.compareTo(BigDecimal.ZERO) == 0){
							lastNum = num;
						}
						lastNum = lastNum.add(num);
						list.add(0,num);
						list.add(1,lastNum);
						depthMap.put(depthPrice,list);

					}

				}
			}else{
				while(limitCount-- > 0){
//					if(depthMap.size() > 0 ){
//						depthPrice = depthMap.lastKey();
//					}
					depthPrice = depthPrice.add(new BigDecimal(level)).setScale(new BigDecimal(level).scale());
					int scale = num.scale();
					Random random = new Random();
					Float f = random.nextFloat();

					BigDecimal randomNum = new BigDecimal(f.toString());

					num = num.add(randomNum);

					num = num.setScale(scale,RoundingMode.DOWN);
					if(depthMap.get(depthPrice) != null){
						List<BigDecimal> list = depthMap.get(depthPrice);
						lastNum = lastNum.add(num);
						list.set(0,num);
						list.set(1,lastNum);
						depthMap.put(depthPrice,list);
					}else{
						List<BigDecimal> list = new ArrayList<BigDecimal>();
						lastNum = lastNum.add(num);
						list.add(0,num);
						list.add(1,lastNum);

						depthMap.put(depthPrice,list);

					}
				}
			}

		}catch (Exception ex){
			throw ex;
		}

		return depthMap;

	}
	private TreeMap<BigDecimal,List<BigDecimal>> limit(TreeMap<BigDecimal,List<BigDecimal>> depthMap,BigDecimal price,String side){
		int limitCount = 20;

		//if(depthMap.size() <= limitCount ) return depthMap;
		//NavigableMap<BigDecimal,BigDecimal> nmap = depthMap.descendingMap();
		if(side.equals(ContractOrderEnum.Side.MORE.getValue())){

			Iterator<BigDecimal> iterator = depthMap.keySet().iterator();

			while(iterator.hasNext()){

				BigDecimal k = iterator.next();
				if(k.compareTo(price) > 0 ){
					iterator.remove();
					depthMap.remove(k);
					continue;
				}
				if(depthMap.size() > limitCount){
					iterator.remove();
					depthMap.remove(k);
				}

			}
		}else{


			NavigableMap<BigDecimal,List<BigDecimal>> nmap = depthMap.descendingMap();
			Iterator<BigDecimal> iterator = nmap.keySet().iterator();
			while(iterator.hasNext()){
				BigDecimal k = iterator.next();
				if(k.compareTo(price) < 0 ){
					iterator.remove();
					depthMap.remove(k);
					continue;
				}
				if(depthMap.size() > limitCount){
					iterator.remove();
					depthMap.remove(k);
				}
			}
		}

		return depthMap ;
	}
	private void cancel(){

	}
	private void expire(){

	}

	//增加做多的深度
	private void addDepthMore(DepthEntity depthEntity ,BigDecimal depthPrice ,BigDecimal num){

	}
	//增加做空的深度
	private void addDepthEmpty(DepthEntity depthEntity, BigDecimal depthPrice,BigDecimal num){

	}


	private DepthEntity initEntityFromRedis(String symbol){
		DepthEntity depthEntity = new DepthEntity();
		try{

			Object entity = null;// redis.opsForValue().get("depth:"+symbol+":"+"entity");
			if(entity == null) {
				depthEntity.setSymbol(symbol);
				depthEntity.setMaxPrice(BigDecimal.ZERO);
				depthEntity.setMinPrice(BigDecimal.ZERO);
				depthEntity.setLevels(new ArrayList<String>());

				depthEntity.setEmpty(new HashMap<String, TreeMap<BigDecimal, BigDecimal>>());
				depthEntity.setMore(new HashMap<String, TreeMap<BigDecimal, BigDecimal>>());
			}else{
				JSONObject jsonObject = JSONObject.parseObject(entity.toString());
				depthEntity.setSymbol(symbol.toUpperCase());
				depthEntity.setMaxPrice(jsonObject.getBigDecimal("maxPrice"));
				depthEntity.setMinPrice(jsonObject.getBigDecimal("minPrice"));
				JSONArray jsonArray = jsonObject.getJSONArray("levels");
				if(jsonArray != null){
					List<String> level  = jsonArray.toJavaList(String.class);
					depthEntity.setLevels(level);
				}
				depthEntity.setMore(new HashMap<String,TreeMap<BigDecimal,BigDecimal>>());
				depthEntity.setEmpty(new HashMap<String,TreeMap<BigDecimal,BigDecimal>>());

				Object more = redis.opsForValue().get("depth:"+symbol+":"+"more");

				if(more != null){
					JSONObject jsonMore = JSONObject.parseObject(more.toString());

					jsonMore.forEach((k,v)->{
						Gson gson = new Gson();

						TreeMap<BigDecimal,BigDecimal> m = gson.fromJson(v.toString(),new TypeToken<TreeMap<BigDecimal,BigDecimal>>(){}.getType());

						depthEntity.getMore().put(k,m);
					});
				}

				Object empty = redis.opsForValue().get("depth:"+symbol+":"+"empty");
				if(empty != null){
					JSONObject jsonEmpty = JSONObject.parseObject(empty.toString());

					jsonEmpty.forEach((k,v)->{
						Gson gson = new Gson();
						TreeMap<BigDecimal,BigDecimal> m = gson.fromJson(v.toString(),new TypeToken<TreeMap<BigDecimal,BigDecimal>>(){}.getType());

						depthEntity.getEmpty().put(k,m);
					});
				}

			}



		}catch (Exception ex){
			log.error("init depth entity from redis error "+ex.getMessage());
		}

		return depthEntity;

	}
	public TreeMap<BigDecimal,List<BigDecimal>> getDepthFromRedis(String symbol,String side,String level){
		TreeMap<BigDecimal,List<BigDecimal>>  depTreeMap = new TreeMap<BigDecimal,List<BigDecimal>>();

		try{
			Object o   = redis.opsForValue().get("depth:"+symbol+":"+side+":"+level);
			if(o != null){
				JSONObject jsonObject = JSONObject.parseObject(o.toString());
				Gson gson = new Gson();
				depTreeMap = gson.fromJson(jsonObject.toJSONString(), new TypeToken<TreeMap<BigDecimal, List<BigDecimal>>>() {
				}.getType());



			}
//			if(depMap != null){
//				depTreeMap.putAll(depMap);
//			}

		}catch (Exception ex){

		}

		return depTreeMap;

	}
	public  void saveDepthToRedis(TreeMap<BigDecimal,List<BigDecimal>> depMap,String symbol,String side,String level){



		redis.opsForValue().set("depth:"+symbol+":"+side+":"+level,JSONObject.toJSONString(depMap));

	}
	private void saveEntityToRedis(DepthEntity depthEntity){
		try{
			String symbol  = depthEntity.getSymbol().toUpperCase();
			JSONObject jsonObject = new JSONObject();
			jsonObject.put("maxPrice",depthEntity.getMaxPrice());
			jsonObject.put("minPrice",depthEntity.getMinPrice());

			jsonObject.put("levels",depthEntity.getLevels());
			redis.opsForValue().set("depth:"+symbol+":"+"entity",jsonObject.toJSONString());

			JSONObject jsonMore = new JSONObject();
		 	depthEntity.getMore().forEach((k,v)->{
				 Gson gson = new Gson();
				 jsonMore.put(k,gson.toJson(v));
			});

			redis.opsForValue().set("depth:"+symbol+":"+"more",jsonMore.toJSONString());


			JSONObject jsonEmpty = new JSONObject();
			depthEntity.getEmpty().forEach((k,v)->{
				Gson gson = new Gson();
				jsonEmpty.put(k,gson.toJson(v));
			});

			redis.opsForValue().set("depth:"+symbol+":"+"empty",jsonEmpty.toJSONString());

		}catch (Exception ex){
			log.error("save depth entity to redis error "+ex.getMessage());
		}
	}
	private void setMaxPrice(DepthEntity depthEntity,BigDecimal maxPrice){
		if(depthEntity.getMaxPrice().compareTo(BigDecimal.ZERO) == 0 || depthEntity.getMaxPrice().compareTo(maxPrice) == -1) {
			depthEntity.setMaxPrice(maxPrice);
		}
	}
	private void setMinPrice(DepthEntity depthEntity,BigDecimal minPrice){
		if(depthEntity.getMinPrice().compareTo(BigDecimal.ZERO) == 0 || depthEntity.getMinPrice().compareTo(minPrice) == 1) {
			depthEntity.setMinPrice(minPrice);
		}
	}
	private BigDecimal calcDepthPrice(String level,BigDecimal orderPrice){
		BigDecimal depthPrice = BigDecimal.ZERO;
		try{
			int level_scale = new BigDecimal(level).stripTrailingZeros().scale();

			if(level_scale > 0 ){
				depthPrice =orderPrice.setScale(level_scale, RoundingMode.DOWN);
				if(depthPrice.compareTo(BigDecimal.ZERO) == 0){
					return depthPrice;
				}
			}else {
				Integer intPrice =orderPrice.intValue();
				String stringPrice = intPrice.toString();
				if (level.equals("1") && intPrice > 0 ) {
					depthPrice = new BigDecimal(intPrice).setScale(0, RoundingMode.DOWN);
				}
				if (level.equals("10") && intPrice > 9 ) {
					stringPrice = stringPrice.substring(0, stringPrice.length() - 1) + "0";
					depthPrice = new BigDecimal(stringPrice).setScale(0, RoundingMode.DOWN);
				}
				if (level.equals("100")) {
					stringPrice = stringPrice.substring(0, stringPrice.length() - 2) + "00";
					depthPrice = new BigDecimal(stringPrice).setScale(0, RoundingMode.DOWN);
				}
			}



		}catch (Exception ex){
			log.error("calc depth price error"+ex.getMessage());
		}

		return depthPrice;
	}
	private final String[] DL = {"0.1","0.01","0.001","0.0001","0.00001","0.000001","0.0000001","0.00000001","0.000000001","0.0000000001","0.00000000001"};
	private final String[] DL_INT = {"1","10","100"};
	private List<String> calcLevel(BigDecimal maxPrice,BigDecimal minPrice){
		List<String> level  = new ArrayList<String>();
		BigDecimal diff = maxPrice.subtract(minPrice);

		if(diff.compareTo(BigDecimal.ZERO) == 0){
			level.add(DL[maxPrice.stripTrailingZeros().scale()-1]);
		 	return level;
		}
		int scale = maxPrice.stripTrailingZeros().scale();

		for(int i = scale ; i>0;i--){
			level.add(DL[i-1]);
		}
		if(diff.intValue()>0){
			level.add(DL_INT[0]);
		}
		if (diff.intValue() > 10) {
			level.add(DL_INT[1]);
		}
		if (diff.intValue() > 100) {
			level.add(DL_INT[2]);
		}
		while(level.size()>5){
			 level.remove(level.size()-2);
		}

//		if(level.size()>5){
//			level = level.subList(0,3);
//			level.add(4,level.get(level.size()-1));
//		}


		return level;

	}



}
